package fr.umlv.m2.jee.spij.framework.dao;

import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

/**
 * @param <K> the primary key type.
 * @param <T> the persistent object type.
 */
public abstract class AbstractDAO<K extends Serializable, T>
        implements IDAO<K, T> {

    /**
     * The entity manager.
     */
    @PersistenceContext
    private EntityManager em;

    /**
     * The entity type.
     */
    private Class<T> entityType;

    /**
     * Default constructor.
     */
    protected AbstractDAO() {
        entityType = (Class<T>)
                ((ParameterizedType) getClass().getGenericSuperclass()).
                        getActualTypeArguments()[1];
    }

    @Override
    public final boolean alreadyExists(final K key) {
        try {
            getEntityManager().getReference(getEntityType(), key);
        } catch (EntityNotFoundException e) {
            return false;
        }
        return true;
    }

    @Override
    public final T getReference(final K id) {
        return getEntityManager().getReference(getEntityType(), id);
    }

    @Override
    public final T findById(final K id) {
        return getEntityManager().find(getEntityType(), id);
    }

    @Override
    @SuppressWarnings("unchecked")
    public final List<T> findByQuery(final String query) {
        Query result = getEntityManager().createQuery(query);
        return (List<T>) result.getResultList();
    }

    @Override
    @SuppressWarnings("unchecked")
    public final T findEntityByQuery(final String query) {
        Query result = getEntityManager().createQuery(query);
        return (T) result.getSingleResult();
    }

    @Override
    @SuppressWarnings("unchecked")
    public final List<T> findByNamedQuery(final String query) {
        Query result = getEntityManager().createNamedQuery(query);
        return (List<T>) result.getResultList();
    }

    @Override
    @SuppressWarnings("unchecked")
    public final T findEntityByNamedQuery(final String query) {
        Query result = getEntityManager().createNamedQuery(query);
        return (T) result.getSingleResult();
    }

    @Override
    @SuppressWarnings("unchecked")
    public final List<T> findAll() {
        StringBuilder query = new StringBuilder("from ")
                .append(getEntityType().getSimpleName());
        Query result = getEntityManager().createQuery(query.toString());
        return (List<T>) result.getResultList();
    }

    @Override
    public final Long findCountAll() {
        StringBuilder query = new StringBuilder("select count(*) from ")
                .append(getEntityType().getSimpleName());
        Query result = getEntityManager().createQuery(query.toString());
        return (Long) result.getSingleResult();
    }

    @Override
    public final T persist(final T toPersist) {
        getEntityManager().persist(toPersist);
        return toPersist;
    }

    @Override
    public final void remove(final T toRemove) {
        getEntityManager().remove(toRemove);
    }

    @Override
    public final void flush() {
        getEntityManager().flush();
    }

    @Override
    public final void clear() {
        getEntityManager().clear();
    }

    @Override
    public final boolean contains(final T isContained) {
        return getEntityManager().contains(isContained);
    }

    @Override
    public final void refresh(final T toRefresh) {
        getEntityManager().refresh(toRefresh);
    }

    @Override
    public final T merge(final T toMerge) {
        return getEntityManager().merge(toMerge);
    }

    @Override
    public final Query createQuery(final String query) {
        return getEntityManager().createQuery(query);
    }

    @Override
    public final Query createNativeQuery(final String query) {
        return getEntityManager().createNativeQuery(query);
    }

    @Override
    public final Query createNamedQuery(final String query) {
        return getEntityManager().createNamedQuery(query);
    }

    @Override
    public final void setEntityManager(final EntityManager entityManager) {
        this.em = entityManager;
    }

    @Override
    public final EntityManager getEntityManager() {
        if (em == null) {
            throw new IllegalStateException("EntityManager has not been set!");
        }
        return em;
    }

    @Override
    public final Class<T> getEntityType() {
        return entityType;
    }
}
